package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @package com.leyou.search.service
 * @description: 导入索引库数据的业务层
 * @author: 许超越
 * @date 2019/6/30 16:14
 * @version: V1.0
 */
@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private GoodsRepository goodsRepository;


    /**
     * 把sup对象转换成goods对象
     *
     * @return
     * @param: SpuDTO
     */
    public Goods buildGoods(SpuDTO spu) {
        //1.商品相关信息的拼接，包含标题，分类，品牌
        //1.1获取所有分类的名称
        String categoryNames = itemClient.queryCategoryByIds(spu.getCategoryIds()).stream().map(CategoryDTO::getName).collect(Collectors.joining(","));
        //1.2获取品牌名称
        String brandName = itemClient.queryBrandById(spu.getBrandId()).getName();
        //1.3获取goods对象中的all字段
        String all = spu.getName() + categoryNames + brandName;
        //2.spu下的所有sku的JSON字符串
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        //2.1查询出spu下的所有sku
        List<SkuDTO> skuList = itemClient.querySkuBySpuId(spu.getId());
        //2.2遍历集合将元素转换成map对象存入skuMapList中
        for (SkuDTO sku : skuList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("price", sku.getPrice());
            map.put("title", sku.getTitle());
            map.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            skuMapList.add(map);
        }
        //2.3将结果转换成JSON格式
        String skus = JsonUtils.toString(skuMapList);
        //3.将当前sku集合下的所有价格收集
        Set<Long> price = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());
        //4.获取当前的spu下的规格参数
        Map<String, Object> specs = new HashMap<>();
        //4.1查询当前spu下所有spec_param
        List<SpecParamDTO> specParamList = itemClient.querySpecParams(null, spu.getCid3(), true);
        //4.2获取spu_detail数据
        SpuDetailDTO spuDetail = itemClient.querySpuDetailById(spu.getId());
        //4.3获取spuDetail中的通用属性规格
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, Object.class);
        //4.4获取spuDetail中的特殊参数
        Map<Long, List<Object>> specialSpecMap = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
        });
        //4.5获取需要过滤的规格参数
        for (SpecParamDTO specParam : specParamList) {
            //获取规格参数的key
            String key = specParam.getName();
            //获取规格参数的value值
            Object value = null;
            //判断是通用属性还是特有属性
            if (specParam.getGeneric()) {
                //是通用的从genericSpecMap中获取
                assert genericSpecMap != null;
                value = genericSpecMap.get(specParam.getId());
            } else {
                //特殊属性从specialSpecParam中获取
                assert specialSpecMap != null;
                value = specialSpecMap.get(specParam.getId());
            }
            //判断是否数字类型
            if (specParam.getGeneric()) {
                //处理赋值为区间
                value = chooseSegment(value, specParam);
            }
            //将key和value添加到specs集合中
            specs.put(key, value);
        }
        //创建goods对象
        Goods goods = new Goods();
        //将spu数据中的相关数据拷贝到goods对象中
        goods.setId(spu.getId());
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setSubTitle(spu.getSubTitle());
        goods.setCreateTime(spu.getCreateTime().getTime());
        //将获取的all字段赋值给goods
        goods.setAll(all);
        //将获取到的skus字段赋值给goods
        goods.setSkus(skus);
        //将获取的price赋值给goods
        goods.setPrice(price);
        //将获取的specs赋值给goods
        goods.setSpecs(specs);
        return goods;
    }

    /**
     * 工具类：将参数为数字类型的转换成当前参数所在的区间
     *
     * @return
     * @param: value 数字类型的参数值
     * @param: p 参数对象
     */
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 将字符串转换成double类型的数字
     *
     * @return
     * @param: str 字符串数字
     */
    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 搜索
     * @param: request 搜索对象
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest request) {

        //1.创建原生搜索构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.组织查询条件
        //2.1source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //2.2搜索条件
        queryBuilder.withQuery(buildBasicQuery(request));
        //2.3分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        //3.搜索结果
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //4.解析结果
        //4.1解析分页数据
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        //5.封装并返回对象
        return new PageResult<>(total, totalPage, BeanHelper.copyWithCollection(goodsList, GoodsDTO.class));
    }

    /**
     * 抽取创建基础查询条件
     * @param: request 查询参数
     * @return
     */
    private QueryBuilder buildBasicQuery(SearchRequest request) {
        //对传递的参数key进行健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        //构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //添加搜索条件
        queryBuilder.must(QueryBuilders.matchQuery("all", key).operator(Operator.AND));
        //获取过滤条件
        Map<String, String> map = request.getFilter();
        if (!CollectionUtils.isEmpty(map)) {
            //遍历集合
            for (Map.Entry<String, String> entry : map.entrySet()) {
                //去除过滤项的名称
                String name = entry.getKey();
                //判断名称类型
                if ("分类".equals(name)) {
                    name = "categoryId";
                } else if ("品牌".equals(name)) {
                    name = "brandId";
                } else {
                    name = "specs." + name;
                }
                //去除过滤项的值
                String value = entry.getValue();
                //添加到过滤条件中
                queryBuilder.filter(QueryBuilders.termQuery(name, value));
            }
        }
        return queryBuilder;
    }


    /**
     * 查询过滤项
     * @return
     * @param: request 搜索条件对象
     */
    public Map<String, List<?>> queryFilters(SearchRequest request) {
        //1.创建接收过滤项的map
        Map<String, List<?>> filterMap = new HashMap<>();
        //2.构建条件查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1过滤查询字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //2.2设置查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        //2.3减少每页查询的内容
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //3.添加聚合
        //3.1添加分类的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        //3.2添加品牌的聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));
        //4.查询
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggs = result.getAggregations();
        //4.1解析分类的聚合
        Terms categoryTerms = aggs.get("categoryAgg");
        //判断是否存在聚合结果
        if (categoryTerms.getBuckets().size() == 0) {
            throw new LyException(ExceptionEnum.CATEGORY_NOT_FOUND);
        }
        List<Long> idList = handleCategoryAgg(categoryTerms, filterMap);
        //4.2解析品牌的聚合
        Terms brandTerms = aggs.get("brandAgg");
        //判断是否存在聚合结果
        if (brandTerms.getBuckets().size() == 0) {
            throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
        }
        handleBrandAgg(brandTerms, filterMap);
        //4.3判断分类是否只有一个来确定是否需要查询规格过滤
        if (idList != null && idList.size() == 1) {
            handleSpecAgg(idList.get(0), basicQuery, filterMap);
        }
        return filterMap;
    }


    /**
     * 获取分类桶的聚合结果
     * @param: categoryTerms 分类的桶信息
     * @param: filterMap 封装分类聚合结果的集合
     * @return: 返回结果将作为其余过滤条件查询依据
     */
    private List<Long> handleCategoryAgg(Terms categoryTerms, Map<String, List<?>> filterMap) {
        //遍历桶，获去分类id
        List<Long> idList = categoryTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //调用服务查询分类,将结果封装进map集合中
        filterMap.put("分类", itemClient.queryCategoryByIds(idList));
        //返回结果
        return idList;
    }


    /**
     * 获取品牌的聚合结果
     * @param: brandTerms 品牌的桶信息
     * @param: filterMap 封装品牌聚合结果的集合
     */
    private void handleBrandAgg(Terms brandTerms, Map<String, List<?>> filterMap) {
        //遍历桶，获取品牌id
        List<Long> idList = brandTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //调用服务查询所有品牌,将结果封装进集合中
        filterMap.put("品牌", itemClient.queryBrandByIds(idList));
    }


    /**
     * 查询规格参数桶过滤
     * @param: cid 分类id
     * @param: basicQuery
     * @param: filterMap
     */
    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterMap) {
        //1.查询当前分类下规格字段默searching字段的值为true的规格名称
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, cid, true);
        //2.构建查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1封装查询条件
        queryBuilder.withQuery(basicQuery);
        //2.2过滤查询字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //2.3让每页显示的数量最少
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //3.构建聚合
        for (SpecParamDTO specParam : specParams) {
            //获取参数名称
            String name = specParam.getName();
            //添加聚合
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        //4.执行查询
        AggregatedPage<Goods> result = elasticsearchTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //4.1获取所有的桶
        Aggregations aggs = result.getAggregations();
        //4.2根据名称分别获取并处理每一个桶
        for (SpecParamDTO specParam : specParams) {
            //根据名称获取桶
            String name = specParam.getName();
            Terms terms = aggs.get(name);
            //取出桶中的数据
            List<String> list = terms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).filter(StringUtils::isNotBlank).collect(Collectors.toList());
            //封装数据返回
            filterMap.put(name, list);
        }
    }


    /**
     * 商品上架，新增索引库数据
     * @param spuId
     */
    public void insertIndex(Long spuId) {
        //根据id查询查询spuId
        //转换成goods
        //存入索引库中
        goodsRepository.save(buildGoods(itemClient.querySpuById(spuId)));
    }


    /**
     * 商品下架，删除索引库中的数据
     * @param spuId
     */
    public void deleteIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
